package com.nulldev.util.java;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import com.nulldev.util.FinalBypass;
import com.nulldev.util.encoding.base64.Base64;
import com.nulldev.util.internal.JDKSupportManager;
import com.nulldev.util.internal.UnsupportedFromJDK;
import com.nulldev.util.io.fastIO.FastIO;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.os.OSUtil;
import com.nulldev.util.os.OSUtil.OSType;
import com.nulldev.util.other.ArrayUtils;

//* For CPU_usage() <br>
//* The following fix is used in eclispe: <br>
//* Go to Window-->Preferences-->Java-->Compiler-->Error/Warnings. Select Deprecated and Restricted API. Change it to warning. Change forbidden and Discouraged Reference and change it to warning.
//* @author null
//*

/**
 */
public class JVMUtil {

	private static Logger logger = LoggerFactory.getLogger(JVMUtil.class, true, true);

	@SuppressWarnings("all")
	@UnsupportedFromJDK(maxSupportedJDK = 1.7)
	@Deprecated
	public static <T> double CPU_usage() {
		JDKSupportManager.createCall();
		if (!ReflectionUtils.classExists("com.sun.management.OperatingSystemMXBean") || JVMUtil.getJavaVer_Double() < 1.7) {
			throw new RuntimeException("com.sun.management.OperatingSystemMXBean is missing. This class is derived from 'rt.jar' in Java till 1.9");
		}
		throw new UnsupportedOperationException("CPU_usage(): WIP feature. Currently requires rt.jar. Working on solution. Current WIP is commented out.");
		//final Class<T> f = ReflectionUtils.fetchClassT("com.sun.management.OperatingSystemMXBean");
		//final Object s = ManagementFactory.getPlatformMXBean((Class<PlatformManagedObject>) f);
		//final Object c = s.getClass().cast(ReflectionUtils.createInstance("com.sun.management.OperatingSystemMXBean"));
	    //final double out = (Double) ReflectionUtils.invokeFunction(c, "getProcessCpuLoad", null);//c.getProcessCpuLoad();
	    //if (out <= 0) {
	    //	logger.warn("Value 'out' is less than 0. This either means your OS is not supported or it is a fallback value.");
	    //}
	    //return out;
		/**
	    final OperatingSystemMXBean osBean = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
	    final double out = osBean.getProcessCpuLoad();
	    if (out <= 0) {
	    	logger.warn("Value 'out' is less than 0. This either means your OS is not supported or it is a fallback value.");
	    }
	    return out;
	    */
	}
	
	public static String getJavaVer() {
		final String s = new String();
		return s.getClass().getPackage().getSpecificationVersion();
	}

	public static double getJavaVer_Double() {
		final String s = new String();
		return MathUtil.safeParseD(s.getClass().getPackage().getSpecificationVersion(), 1.7);
	}

	public static String fromError(final Throwable error) {
		final StringWriter sw = new StringWriter();
		final PrintWriter pw = new PrintWriter(sw);
		error.printStackTrace(pw);
		final String exceptionAsString = sw.toString();
		pw.close();
		try {
			sw.close();
		} catch (IOException e) {
		}
		return exceptionAsString;
	}
	
	public static String fromErrorToBase64(final Throwable error) {
		return Base64.encodeBase64S(fromError(error));
	}

	public static void halt() {
		Runtime.getRuntime().halt(-1);
	}
	
	public static boolean isExtended(final Class cls, final Class toFind) {
		return getSuperClass(cls).equals(toFind.getName());
	}

	public static String getSuperClass(final Class cls) {
		final FinalBypass<String> superClass = new FinalBypass<String>(cls.getName());
		final FinalBypass<Class> supercls = new FinalBypass<Class>(cls);
		boolean errorHit = false;
		while (!errorHit) {
			try {
				final Class t = supercls.getObject().getSuperclass();
				if (Variables.isNull(t)) {
					throw new Exception();
				}else if (t.getName().equals("java.lang.Object")) {
					throw new Exception();
				}
				supercls.setObject(t);
			}catch (Exception e) {
				errorHit = true;
				superClass.setObject(supercls.getObject().getName());
			}
		}
		return superClass.getObject();
	}

	public static void printError(final Throwable e) {
		async_error(fromError(e));
	}

	private static final String cache_lineSep = System.getProperty("line.separator");
	public static final String LINE_SEP_BASIC = "\n";
	public static final String LINE_SEP_WINDOWS = "\r\n";
	public static final String LINE_SEP_UNIX = "\n";
	public static String lineSep() {
		return cache_lineSep;
	}

	public static String getProperty(final String name) {
		return System.getProperty(name);
	}

	public static void setProperty(final String name, final String newValue) {
		System.setProperty(name, newValue);
	}

	/**
	 * Beta Feature
	 * @param pid [Variable for attach(final String arg)]
	 * @returns VirtualMachine instance
	 */
	@Deprecated
	@UnsupportedFromJDK(maxSupportedJDK = 1.8)
	public static Object attach(final String pid){
		JDKSupportManager.createCall();
		if (!ReflectionUtils.classExists("com.sun.tools.attach.VirtualMachine")) {
			throw new RuntimeException("com.sun.tools.attach.VirtualMachine is missing. This class is derived from 'tools.jar'.");
		}
		final Class<?> c = ReflectionUtils.fetchClass("com.sun.tools.attach.VirtualMachine");
		final Object output = ReflectionUtils.invokeFunction(c, "attach", pid);
		return output;
	}

	public static void debug_Prop() {
		final Properties p = System.getProperties();
		final Enumeration keys = p.keys();
		while (keys.hasMoreElements()) {
		    String key = (String)keys.nextElement();
		    String value = (String)p.get(key);
		    FastIO.get().println(key + ": " + value);
		}
	}
	
	public static void debug_ENV() {
        Map<String, String> map = System.getenv();
        for(Map.Entry <String, String> entry: map.entrySet()) {
            FastIO.get().println(entry.getKey() + " = " + entry.getValue());
        }
	}

	public static boolean isKPITsafe() {
		if (OSUtil.getOperatingSystemType() == OSType.Windows) {
			return System.getProperty("sun.cpu.isalist").contains("amd64");
		}else {
			return System.getProperty("sun.arch.data.model").contains("64");
		}		
	}
	
	 /**
	 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
	 *
	 * @param packageName The base package
	 * @return The classes
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	@Deprecated
	private static Class[] getClasses(String packageName)
	        throws ClassNotFoundException, IOException {
		logger.error("getClasses(package) -> This function is marked for removal. [Reason: Does not work within real enviroment, returns null. Replaced by ReflectionsUtil.ClassUtil.getClassesForPackage()]");
	    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	    final ArrayList<Class> output = new ArrayList<Class>();
		if (classLoader == null) {
			classLoader = JVMUtil.class.getClassLoader();
		}
	    assert classLoader != null;
	   // logger.info("getClasses(packages) -> debug(str) -> Assert passed.");
	    String path = packageName.replace('.', '/');
	    //logger.info("getClasses(packages) -> debug(str) -> Path corrected.");
	    Enumeration<URL> resources = classLoader.getResources(path);
	   // logger.info("getClasses(packages) -> debug(str) -> Fetched resources.");
	    List<File> dirs = new ArrayList<File>();
	    while (resources.hasMoreElements()) {
	        URL resource = resources.nextElement();
	       // logger.info("getClasses(packages) -> debug(str) -> GOT URL");
	        if (resource != null && resource.getFile() != null) {
	        	// logger.info("getClasses(packages) -> debug(str) -> ADDED URL");
	        	 dirs.add(new File(resource.getFile()));
	        }else {
	        	logger.warn("getClasses(packages) -> Skipped a blank resource!");
	        }
	    }
	    ArrayList<Class> classes = new ArrayList<Class>();
	    for (File directory : dirs) {
	       // classes.addAll(findClasses(directory, packageName));
	    	final List<Class> clx = findClasses(directory, packageName);
	    	if (clx == null) {
	    		logger.warn("getClasses(packages) -> Skipped a resource loop due to null exception!");
	    	}else {
		    	for (final Class cl : clx) {
		    		if (cl != null) {
		    			classes.add(cl);
		    		}else {
		    			logger.warn("getClasses(packages[]) Hit a null class!");
		    		}
		    	}
	    	}
	    }
	    //output.add(classes.toArray(new Class[classes.size()]));
	    output.addAll(classes);
//	    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//	    assert classLoader != null;
//	    String path = packageName.replace('.', '/');
//	    Enumeration<URL> resources = classLoader.getResources(path);
//	    List<File> dirs = new ArrayList<File>();
//	    while (resources.hasMoreElements()) {
//	        URL resource = resources.nextElement();
//	        dirs.add(new File(resource.getFile()));
//	    }
//	    ArrayList<Class> classes = new ArrayList<Class>();
//	    for (File directory : dirs) {
//	        classes.addAll(findClasses(directory, packageName));
//	    }
//	    return classes.toArray(new Class[classes.size()]);
	    return ArrayUtils.arrayListToArray(output);
	}
	
	@Deprecated
	private static Class[] getClasses(String... packages) throws ClassNotFoundException, IOException {
		logger.error("getClasses(package[]) -> This function is marked for removal. [Reason: Does not work within real enviroment, returns null. Replaced by ReflectionsUtil.ClassUtil.getClassesForPackage()]");
	    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	    final ArrayList<Class> output = new ArrayList<Class>();
	    for (final String packageName : packages) {
    		if (classLoader == null) {
    			classLoader = JVMUtil.class.getClassLoader();
    		}
		    assert classLoader != null;
		   // logger.info("getClasses(packages) -> debug(str) -> Assert passed.");
		    String path = packageName.replace('.', '/');
		    //logger.info("getClasses(packages) -> debug(str) -> Path corrected.");
		    Enumeration<URL> resources = classLoader.getResources(path);
		   // logger.info("getClasses(packages) -> debug(str) -> Fetched resources.");
		    List<File> dirs = new ArrayList<File>();
		    while (resources.hasMoreElements()) {
		        URL resource = resources.nextElement();
		       // logger.info("getClasses(packages) -> debug(str) -> GOT URL");
		        if (resource != null && resource.getFile() != null) {
		        	// logger.info("getClasses(packages) -> debug(str) -> ADDED URL");
		        	 dirs.add(new File(resource.getFile()));
		        }else {
		        	logger.warn("getClasses(packages) -> Skipped a blank resource!");
		        }
		    }
		    ArrayList<Class> classes = new ArrayList<Class>();
		    for (File directory : dirs) {
		       // classes.addAll(findClasses(directory, packageName));
		    	final List<Class> clx = findClasses(directory, packageName);
		    	if (clx == null) {
		    		logger.warn("getClasses(packages) -> Skipped a resource loop due to null exception!");
		    	}else {
			    	for (final Class cl : clx) {
			    		if (cl != null) {
			    			classes.add(cl);
			    		}else {
			    			logger.warn("getClasses(packages[]) Hit a null class!");
			    		}
			    	}
		    	}
		    }
		    //output.add(classes.toArray(new Class[classes.size()]));
		    output.addAll(classes);
	    }
	    return ArrayUtils.arrayListToArray(output);
	}

	/**
	 * Recursive method used to find all classes in a given directory and subdirs.
	 *
	 * @param directory   The base directory
	 * @param packageName The package name for classes found inside the base directory
	 * @return The classes
	 * @throws ClassNotFoundException
	 */
	@Deprecated
	private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
		logger.error("findClasses(package) -> This function is marked for removal. [Reason: Does not work within real enviroment, returns null. Replaced by ReflectionsUtil.ClassUtil.getClassesForPackage()]");
	    List<Class> classes = new ArrayList<Class>();
	    if (!directory.exists()) {
	        return classes;
	    }
	    File[] files = directory.listFiles();
	    for (File file : files) {
	        if (file.isDirectory()) {
	            assert !file.getName().contains(".");
	            classes.addAll(findClasses(file, packageName + "." + file.getName()));
	        } else if (file.getName().endsWith(".class")) {
	            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
	        }
	    }
	    return classes;
	}

	public static void syncdErr(final Runnable r) {
		synchronized (System.err) {
			synchronized (System.out) {
				r.run();
			}
		}
	}

	public static String getSelf() {
		try {
			return JVMUtil.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
		} catch (URISyntaxException e) {
			e.printStackTrace();
			return "N/A";
		}
	}

	public static String getArgs_C() {
		final List<String> args = ManagementFactory.getRuntimeMXBean().getInputArguments();
		final StringBuilder out = new StringBuilder();
		for (final String arg : args) {
			out.append(arg + " ");
		}
		return out.toString();
	}
	
	private static String[] args = new String[0];

	public static void exit() {
		System.exit(0);
	}

	public static String[] getArgsList_C() {
		return ArrayUtils.listToArray(ManagementFactory.getRuntimeMXBean().getInputArguments());
	}
	
	public static void setArgs(final String[] args) {
		JVMUtil.args = args;
	}
	
	public static String[] getArgsList() {
		return args;
	}
	
	public static boolean isAllowed(final Permission perm) {
		try {
			System.getSecurityManager().checkPermission(perm);
			return true;
		}catch (SecurityException e) {
			return false;
		}
	}

	public static boolean isArg(final String string) {
		for (final String s : getArgsList()) {
			if (s.equals(string)) {
				return true;
			}
		}
		return false;
	}

	@Deprecated
	private static Class[] getClassesS(final String... classes) {
		try {
			return getClasses(classes);
		}catch (Exception e) {
			e.printStackTrace();
			return new Class[0];
		}
	}

	public static void debug_loc_fetch(final String path) throws IOException {
		final File jarFile = new File(JVMUtil.class.getProtectionDomain().getCodeSource().getLocation().getPath());

		if(jarFile.isFile()) {
		    final JarFile jar = new JarFile(jarFile);
		    final Enumeration<JarEntry> entries = jar.entries();
		    while(entries.hasMoreElements()) {
		        final String name = entries.nextElement().getName();
		        if (name.startsWith(path + "/")) {
		            System.out.println(name);
		        }
		    }
		    jar.close();
		} else {
		    final URL url = JVMUtil.class.getResource("/" + path);
		    if (url != null) {
		        try {
		            final File apps = new File(url.toURI());
		            for (File app : apps.listFiles()) {
		                System.out.println(app);
		            }
		        } catch (URISyntaxException ex) {
		        }
		    }
		}
	}

	public static void throwIfAny(final List<Throwable> errors) {
		if (errors == null || errors.isEmpty()) {
			for (final Throwable tr : errors) {
				if (tr != null) {
					throw new RuntimeException(tr);
				}
			}
		}
	}

	public static void async_println(final String data) {
		FastIO.get().println(data);
		//synchronized (System.out) {
		//	synchronized (System.err) {
		//		System.out.println(data);
		//	}
		//}
	}
	
	public static void async_println(final Object data) {
		FastIO.get().println(data);
		//synchronized (System.out) {
		//	synchronized (System.err) {
		//		System.out.println(data);
		//	}
		//}
	}
	
	public static void async_print(final Object data) {
		FastIO.get().print(data);
		//synchronized (System.out) {
		//	synchronized (System.err) {
		//		System.out.print(data);
		//	}
		//}
	}

	public static boolean is64() {
		return System.getProperty("os.arch").equalsIgnoreCase("amd64");
	}

	public static String getLibLoc() {
		if (!OSUtil.isLinux()) {
			return null;
		}else {
			if (is64()) {
				return "/usr/java/packages/lib/amd64/";
			}else {
				return "/usr/java/packages/lib/i386/";
			}
		}
	}

	public static void addArg(final String string) {
		JVMUtil.args = ArrayUtils.add(JVMUtil.args, string);
	}

	public static void async_error(final Object object) {
		FastIO.get().println(System.err, object);
		//synchronized (System.out) {
		//	synchronized (System.err) {
		//		System.err.println(object);
		//	}
		//}
	}
}
